#pragma once

#include <cctype>
#include <string>
#include <istream>
#include <ostream>
#include <boost/asio/read_until.hpp>

namespace alpha { namespace http {

namespace detail {
	
template <typename Stream, typename String>
Stream & gethttpline(Stream &stream, String &s)
{
	std::getline(stream, s);
	typename String::size_type size = s.size();
	if (size && s[size - 1] == '\r')
		s.resize(size - 1);
	return stream;
}

template <typename AsyncReadStream, typename Buffer, typename HTTP_Headers, typename ReadHandler>
class async_read_headers_handler
{
public:
	async_read_headers_handler(AsyncReadStream &stream, Buffer &streambuf, HTTP_Headers &headers, ReadHandler handler)
		: stream_(stream), streambuf_(streambuf), headers_(headers), handler_(handler)
	{}
	void operator()(const boost::system::error_code& ec, std::size_t bytes_transferred)
	{
		// check for errors.
		if (ec)
		{
			handler_(ec, bytes_transferred);
			return;
		}
		// read headers
		do {
			// get the line first
			std::istream ss(&streambuf_);
			std::string line;
			gethttpline(ss, line);
			if (!ss)
				break;
			// ending line
			if (line.empty())
			{
				// notification
				handler_(ec, 0);
				return;
			}
			// leading spaces, add value to the last key
			// line cannot be empty
			std::string::size_type pos;
			if (std::isspace(line[0])) //(ch == 9 || ch == 32)
			{
				// cannot be the first line
				if (field_.empty())
					break;
				pos = 1;
			}
			// read field name
			else 
			{
				pos = line.find(':');
				if (pos == std::string::npos)
					break;
				field_ = line.substr(0, pos);
				++pos;
			}
			// skip spaces after the field name
			for (; pos < line.size() && std::isspace(line[pos]); ) //(ch == 9 || ch == 32)
				++pos;
			// create / append
			headers_[field_] += line.substr(pos);
			// read the next line
			boost::asio::async_read_until(stream_, streambuf_, '\n', *this);
			return;
		} while(0);
		// error
		handler_(boost::system::error_code(EILSEQ, boost::system::errno_ecat), bytes_transferred);
	}
//private:
	AsyncReadStream &stream_;
	Buffer &streambuf_;
	HTTP_Headers &headers_;
	ReadHandler handler_;
	typename HTTP_Headers::key_type field_;
};

template <typename AsyncReadStream, typename Buffer, typename HTTP_Headers, typename ReadHandler>
inline void * asio_handler_allocate(std::size_t size, async_read_headers_handler<AsyncReadStream, Buffer, HTTP_Headers, ReadHandler>* this_handler)
{
	return boost_asio_handler_alloc_helpers::allocate(size, &this_handler->handler_);
}

template <typename AsyncReadStream, typename Buffer, typename HTTP_Headers, typename ReadHandler>
inline void asio_handler_deallocate(void * pointer, std::size_t size, async_read_headers_handler<AsyncReadStream, Buffer, HTTP_Headers, ReadHandler>* this_handler)
{
	boost_asio_handler_alloc_helpers::deallocate(pointer, size, &this_handler->handler_);
}

template <typename Function, typename AsyncReadStream, typename Buffer, typename HTTP_Headers, typename ReadHandler>
inline void asio_handler_invoke(const Function& function, async_read_headers_handler<AsyncReadStream, Buffer, HTTP_Headers, ReadHandler>* this_handler)
{
	asio_handler_invoke_helpers::invoke(function, &this_handler->handler_);
}

} // namespace detail

template <typename Async_Read_Stream, typename Buffer, typename HTTP_Headers, typename Handler>
void async_read_headers(Async_Read_Stream &s, Buffer &b, HTTP_Headers &r, Handler handler)
{
	boost::asio::async_read_until(s, b, '\n', detail::async_read_headers_handler<Async_Read_Stream, Buffer, HTTP_Headers, Handler>(s, b, r, handler));
}

template <typename Buffer, typename HTTP_Headers>
void write_headers(Buffer &b, HTTP_Headers const &headers)
{
	std::ostream ss(&b);
	for (typename HTTP_Headers::const_iterator i = headers.begin(); i != headers.end(); ++i)
		ss << i->first << ": " << i->second << "\r\n";
	ss << "\r\n";
}

}} // namespace alpha::http
